use crate::qtrade::strategy::util::valley_klines::calculate_sma;
use crate::qtrade::strategy::util::valley_klines::find_crossover_indices;
use crate::qtrade::strategy::util::valley_klines::find_crossunder_indices;
use crate::qtrade::strategy::util::valley_klines::find_lowest;
use crate::todo::MAX_KLINE_DAYS;

use super::util::valley_klines::find_highest;
use super::BacktestResult;
use super::Strategy;
use super::StrategyPerformance;
use anyhow::Result;
use anyhow::anyhow;

use chrono::DateTime;
use chrono::Utc;
use fudata::db::malines::lsma::Lsma;
use fudata::db::malines::sma::Sma;
use fudata::db;
use fudata::db::malines::smpr::Smpr;
use fudata::model::kline::Klt;
use fudata::model::kline::Kline;
use fudata::model::malines::ma::LSMAv;
use fudata::model::malines::ma::MAConfig;
use fudata::model::malines::ma::MA;

use fudata::model::trade::trade_storder::SharpDropReboundBuy;
use fudata::model::trade::trade_storder::StopLossSell;
use fudata::model::trade::trade_storder::TradeStorder;
use fudata::model::trade::trade_storder::TradeStorderData;
use fudata::model::trade::GoldenRatio;
use fudata::round2;
use fudata::round4;
use log::info;
use serde::Deserialize;
use serde::Serialize;
use surrealdb::sql::Thing;
use yata::methods::CrossAbove;
use yata::core::Method;
use yata::methods::CrossUnder;

/// 急跌反弹策略
/// 参数：短均线/长均线： 5/21，小时放量倍数
/// 策略：
/// 1. 60分钟的短均线向上突破长均线，买入，最近有小时放量倍数大于平均量5倍，买入加权
/// 2. 15分钟的短均线向下跌破长均线，卖出
pub struct SharpDropRebounceStrategy;

impl Strategy for SharpDropRebounceStrategy {
    async fn backtest(&self, security_id: &str) -> Result<BacktestResult> {
        println!("[{}] backtest  for moving average strategy.", security_id);
        let worker = SharpDropRebounceStrategyWorker::new(security_id, &Klt::M060).await;

        // This function would return some kind of result.
        // For demonstration purposes, we just return a dummy value.
        Ok(BacktestResult {
            profit: 123.0,
            win_rate: 68.0,
            avg_holding_days: 18.0,
        })
    }
}

#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct SDRPerformanceSummary {
    pub security_id: String,
    /// 当前放量Kline
    pub kline: Kline,

    // down left highest kline
    pub dlh_kline: Kline,
    // down left low kline
    pub dll_kline: Kline,
}

impl SDRPerformanceSummary {
    /// 最大跌幅
    pub fn dllow_high_ratio(&self) -> f64 {
        round4((self.dlh_kline.high - self.dll_kline.low) / self.dlh_kline.high)
    }

    /// 最大跌幅靠近哪个黄金分割数
    pub fn dllow_high_phi(&self) -> GoldenRatio {
        let ratio = self.dllow_high_ratio();
        GoldenRatio::nearest_phi(ratio)
    }

    /// 当前跌幅
    pub fn curr_high_ratio(&self) -> f64 {
        round4((self.dlh_kline.high - self.kline.low) / self.dlh_kline.high)
    }

    /// 当前跌幅靠近哪个黄金分割数
    pub fn curr_high_phi(&self) -> GoldenRatio {
        let ratio = self.curr_high_ratio();
        GoldenRatio::nearest_phi(ratio)
    }
}
pub struct SharpDropRebounceStrategyWorker {
    security_id: String,
    /// all klines
    all_klines: Vec<Kline>,

    /// volume 均值
    volume_ma: Sma,

    /// close 均值5日下穿均值21日
    crossunders: Vec<(String, usize)>,
    /// close 均值5日上穿均值21日
    crossovers: Vec<(String, usize)>,

    ///根据急跌反弹策略的买卖订单
    trade_orders: Vec<TradeStorder>,
    /// 统计一买一卖的盈亏
    performances: Vec<StrategyPerformance>,
}

impl SharpDropRebounceStrategyWorker {
    pub async fn new(security_id: &str, klt: &Klt) -> Result<Self> {
        let mut all_klines = db::kline::select(
            security_id,
            None,
            Some(klt),
            Some("klts DESC"),
            0,
            MAX_KLINE_DAYS as u16
        ).await?;
        all_klines.reverse();

        if let Some(fstkl) = all_klines.first() {
            let volume_ma: Sma = Sma::new(
                &(MAConfig { periods: vec![5, 21] }),
                &(fstkl.volume as f64)
            )?;

            let sma5s = calculate_sma(&all_klines, 5);
            for (idx, ov) in sma5s.iter().enumerate() {
                if idx < 3 || idx >= sma5s.len() - 3 {
                    // info!("Sma5[{idx}] {:?}", ov);
                }
            }
            // info!("");
            let sma21s = calculate_sma(&all_klines, 21);
            for (idx, ov) in sma21s.iter().enumerate() {
                if idx < 3 || idx >= sma21s.len() - 3 {
                    // info!("Sma21[{idx}] {:?}", ov);
                }
            }

            // info!("\ncrossovers*********");
            let crossovers = find_crossover_indices(&sma5s, &sma21s);
            // crossovers.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
            for (idx, ov) in crossovers.iter().enumerate() {
                // info!("Crossover[{idx}] {:?}", ov);
            }

            // info!("\ncrossunders*********");
            let crossunders = find_crossunder_indices(&sma5s, &sma21s);
            // crossunders.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));

            for (idx, ov) in crossunders.iter().enumerate() {
                // info!("Crossunders[{idx}] {:?}", ov);
            }

            return Ok(SharpDropRebounceStrategyWorker {
                security_id: security_id.to_string(),
                all_klines,

                volume_ma,

                crossovers,
                crossunders,
                trade_orders: Vec::new(),
                performances: Vec::new(),
            });
        }
        Err(anyhow!("no kline day data"))
    }

    pub fn is_last_buy(&self) -> bool {
        match self.trade_orders.last() {
            Some(to) =>
                match to.data {
                    Some(TradeStorderData::SharpDropReboundBuy(_)) => true,

                    _ => false,
                }
            None => false,
        }
    }

    pub async fn run_perfs(&mut self) -> Result<Vec<SDRPerformanceSummary>> {
        let mut result = Vec::new();

        for (idx, kline) in self.all_klines.iter().enumerate() {
            // volume
            let the_volume = kline.volume as f64;
            let volume_v: MA<f64> = self.volume_ma.next(&the_volume);
            let volumev5 = volume_v.mav(5).unwrap_or_else(|| 0.0);
            let volumev21 = volume_v.mav(21).unwrap_or_else(|| 0.0);

            if let Some(dlh) = self.find_down_left_highest(idx) {
                // info!("found dlh={:?}", dlh);
                let dlh_kline = &self.all_klines[dlh.1];
                if
                    (dlh_kline.high - kline.low) / dlh_kline.high > 0.3 //0.18 // 0.4
                {
                    // 接近黄金点位GoldenRatio::Phi0191,
                    if let Some(dll) = find_lowest(&self.all_klines, dlh.1, idx, Some(false)) {
                        let dlleft = &self.all_klines[dll.1];
                        // 红盘的收盘价吞没最低Kline，阳包阴
                        if kline.close > kline.open && kline.close >= dlleft.open.max(dlleft.close) {
                            let sbuy = SDRPerformanceSummary {
                                security_id: self.security_id.clone(),
                                kline: kline.clone(),
                                dlh_kline: dlh_kline.clone(),
                                dll_kline: dlleft.clone(),
                            };
                            // println!("\nBUY@{:?} ", sbuy);
                            result.push(sbuy);
                        }
                    }
                }
            }
        }

        Ok(result)
    }

    pub async fn run_buy_sell(&mut self) {
        for (idx, kline) in self.all_klines.iter().enumerate() {
            // volume
            let the_volume = kline.volume as f64;
            let volume_v: MA<f64> = self.volume_ma.next(&the_volume);
            let volumev5 = volume_v.mav(5).unwrap_or_else(|| 0.0);
            let volumev21 = volume_v.mav(21).unwrap_or_else(|| 0.0);

            let kline_id = kline.id_raw();
            let storder_id = TradeStorder::id_from(kline_id.as_str());

            // 黄金分割跌幅抄底买入
            if !self.is_last_buy() {
                if let Some(dlh) = self.find_down_left_highest(idx) {
                    // info!("found dlh={:?}", dlh);
                    let dlh_kline = &self.all_klines[dlh.1];
                    // 1. 跌幅接近黄金点位GoldenRatio::Phi0191开始抄底反弹,
                    if
                        let Some(dll) = find_lowest(&self.all_klines, dlh.1, idx, Some(false)) // 查找最低阴线
                    {
                        let dll_kline = &self.all_klines[dll.1];
                        if (dlh_kline.high - dll_kline.low) / dlh_kline.high > 0.3 {
                            // 2. 红盘的收盘价吞没最低Kline，阳包阴买入
                            if
                                kline.close > kline.open && //红盘
                                kline.close >= dll_kline.open.max(dll_kline.close) && //收盘价在最低Kline的实体上方
                                kline.open < dll_kline.open
                            {
                                let buy = TradeStorder {
                                    id: Some(Thing::from(("trade_stbuy", storder_id.as_str()))),
                                    security_id: self.security_id.clone(),
                                    kline_id: kline_id.clone(),
                                    klts: kline.klts,

                                    st_price: kline.close,
                                    st_volume: 100,

                                    data: Some(
                                        TradeStorderData::SharpDropReboundBuy(SharpDropReboundBuy {
                                            kline: kline.clone(),
                                            dlh_kline: dlh_kline.clone(),
                                            dll_kline: dll_kline.clone(),
                                            stop_price: dll_kline.low,
                                            message: format!("当前Kline阳包阴最低价Kline买入"),
                                        })
                                    ),
                                };
                                self.trade_orders.push(buy);
                            }
                        }
                    }
                }
            }

            // 判断有否最新的抄底买入单需要止损
            if self.is_last_buy() {
                if let Some(to) = self.trade_orders.last_mut() {
                    match &to.data {
                        Some(TradeStorderData::SharpDropReboundBuy(buy)) => {
                            if
                                kline.close < buy.stop_price //测试较好
                                // if kline.low < buy.stop_price //测试很不好大亏
                            {
                                let sell = TradeStorder {
                                    id: Some(Thing::from(("trade_stbuy", storder_id.as_str()))),
                                    security_id: self.security_id.clone(),
                                    kline_id: kline_id.clone(),
                                    klts: kline.klts,

                                    st_price: kline.close, // when if kline.close < buy.stop_price， 用当天收盘价作为第二天止损价，不容易被震荡出去
                                    // st_price: buy.stop_price, // when if kline.low < buy.stop_price, 用当天盘中价来决定卖很容易被震荡出去
                                    st_volume: 100,

                                    data: Some(
                                        TradeStorderData::StopLossSell(StopLossSell {
                                            buyorder_id: to.id_raw(),
                                            message: format!("跌穿前面阴线最低价止损卖出"),
                                        })
                                    ),
                                };

                                // volume=1
                                // #,security_id, profit_loss_ratio, win_rate, total_profit, total_loss, total_fee, total_trades
                                // 0, SZ.STK.300363, 1.66, 40.79, 8921.00, -5372.00, 295.57, 76
                                // 1, SH.STK.603806, 1.33, 53.06, 3799.00, -2847.00, 140.09, 49
                                // 2, SZ.STK.002648, 1.03, 41.18, 2117.00, -2064.00, 79.80, 51
                                let volume = buy.dllow_high_phi().drop_buy_share();
                                // using kline.close as stop_price ， 用当天收盘价作为第二天止损价，不容易被震荡出去
                                // #,security_id, profit_loss_ratio, win_rate, total_profit, total_loss, total_fee, total_trades
                                // 0, SZ.STK.300363, 2.04, 40.79, 252.40, -123.66, 7.45, 76
                                // 1, SH.STK.603806, 1.61, 53.06, 93.55, -58.16, 3.09, 49
                                // 2, SZ.STK.002648, 1.44, 41.18, 32.38, -22.46, 0.99, 51
                                // #,security_id, profit_loss_ratio, win_rate, total_profit, total_loss, total_fee, total_trades
                                // 0, SZ.STK.300363, 2.07, 38.55, 257.70, -124.65, 7.93, 83
                                // 1, SH.STK.603806, 1.62, 47.17, 92.93, -57.46, 3.37, 53
                                // 2, SZ.STK.002648, 1.35, 35.71, 32.60, -24.19, 1.04, 56
                                // #,security_id, profit_loss_ratio, win_rate, total_profit, total_loss, total_fee, total_trades
                                // 0, SZ.STK.300363, 2.34, 38.55, 286.10, -122.02, 7.96, 83
                                // 1, SH.STK.603806, 1.64, 46.30, 93.15, -56.94, 3.33, 54
                                // 2, SZ.STK.002648, 1.43, 38.60, 33.58, -23.46, 1.06, 57
                                // #,security_id, profit_loss_ratio, win_rate, total_profit, total_loss, total_fee, total_trades
                                // 0, SZ.STK.300363, 2.34, 39.02, 286.00, -122.04, 7.94, 82
                                // 1, SH.STK.603806, 1.82, 46.30, 103.77, -56.94, 3.34, 54
                                // 2, SZ.STK.002648, 1.42, 38.60, 33.42, -23.46, 1.06, 57
                                // using kline.low && buy.stop_price , 用当天盘中价来决定卖很容易被震荡出去，大亏
                                // #,security_id, profit_loss_ratio, win_rate, total_profit, total_loss, total_fee, total_trades
                                // 0, SZ.STK.002648, 1.94, 31.03, 31.61, -16.32, 1.08, 58
                                // 1, SZ.STK.300363, 0.99, 27.66, 173.25, -174.44, 8.30, 94
                                // 2, SH.STK.603806, 0.65, 32.79, 45.24, -69.19, 3.77, 61
                                // 所有看来仓位管理非常重要，越跌仓位要越大

                                let perf = StrategyPerformance {
                                    buyorder_id: to.id_date(),
                                    buy_price: to.st_price,
                                    sellorder_id: sell.id_date(),
                                    sell_price: sell.st_price,
                                    volume,
                                    message: format!(
                                        "{:?},{}",
                                        buy.curr_high_phi(),
                                        round2(buy.curr_high_ratio() * 100.0)
                                    ),
                                };

                                self.performances.push(perf);

                                self.trade_orders.push(sell);
                            }
                        }
                        _ => {}
                    }
                }
            }

            // 更新移动止损
            if self.is_last_buy() {
                match self.trade_orders.last_mut() {
                    Some(to) => {
                        match &mut to.data {
                            Some(TradeStorderData::SharpDropReboundBuy(buy)) => {
                                // 收盘价回撤3个点还大于止盈价就再更新止盈价，移动向上止盈
                                if
                                    kline.close > kline.open && // 红盘
                                    kline.high * 0.97 > buy.stop_price
                                {
                                    buy.stop_price = round2(kline.high * 0.97);
                                } else if
                                    kline.close < kline.open && //绿盘
                                    (kline.high - kline.close) / kline.high > 0.03 && //高点回撤3个点
                                    kline.close * 0.97 > buy.stop_price
                                {
                                    buy.stop_price = round2(kline.close * 0.97);
                                }
                            }
                            _ => {}
                        };
                    }
                    // #,security_id, profit_loss_ratio, win_rate, total_profit, total_loss, total_fee, total_trades
                    // 0, SZ.STK.300363, 2.34, 39.02, 286.00, -122.04, 7.94, 82
                    // 1, SH.STK.603806, 1.82, 46.30, 103.77, -56.94, 3.34, 54
                    // 2, SZ.STK.002648, 1.42, 38.60, 33.42, -23.46, 1.06, 57
                    // 3, SH.STK.601555, 0.07, 16.13, 1.39, -21.00, 0.74, 31

                    None => {}
                }
            }
        }
    }

    ///
    /// 查找当前Kline的最近左侧下跌最高Kline
    ///
    pub fn find_down_left_highest(&self, kline_idx: usize) -> Option<(String, usize)> {
        let mut crossunder = None;
        let mut crossover = None;
        for under in self.crossunders.iter() {
            if under.1 < kline_idx {
                crossunder = Some(under);
            }
        }
        for over in self.crossovers.iter() {
            if over.1 < kline_idx {
                crossover = Some(over);
            }
        }

        if let (Some(over), Some(under)) = (crossover, crossunder) {
            // info!("over={:?} under={:?} kline_idx={:?}", over, under, kline_idx);
            if over.1 < under.1 {
                let left_highest = find_highest(&self.all_klines, over.1, kline_idx);
                return left_highest;
            }
        }

        None
    }
}

#[cfg(test)]
mod test {
    use std::collections::HashMap;

    use chrono::DateTime;
    use chrono::Days;
    use chrono::Utc;
    use fudata::db;
    use fudata::model::kline::Klt;
    use fudata::model::security;
    use fudata::model::security::Market;
    use fudata::model::security::Security;
    use log::info;
    use log::trace;
    use surrealdb::sql::Thing;
    use crate::qtrade::strategy::get_strategy_test_securities;
    use crate::qtrade::strategy::print_strategy_performance_summary;
    use crate::qtrade::strategy::sharp_drop_rebounce::SharpDropRebounceStrategyWorker;
    use crate::todo::MAX_KZZ_SECURITY;
    use crate::todo::MAX_STK_SECURITY;

    #[tokio::test]
    async fn test_run_perfs() {
        let _ = db::init().await.unwrap();

        pretty_env_logger::init();

        let securities = get_strategy_test_securities(
            true,
            Some(Market::SH),
            Some(security::SecurityType::STK),
            0,
            30
        ).await;

        let mut all_perfs = Vec::new();
        for security in securities.iter() {
            let security_id = security.id_raw();

            let rs = SharpDropRebounceStrategyWorker::new(security_id.as_str(), &Klt::DAY).await;

            match rs {
                Ok(mut worker) => {
                    let buys = worker.run_perfs().await.unwrap();
                    all_perfs.extend(buys);
                }

                Err(e) => {
                    println!("err={:?}", e);
                }
            }
        }

        println!("KZZ all_perfs.len()={:?}", all_perfs.len());
        println!("#, security_id, HLC-Date, H/L-Phi,H/L-Ratio, H/C-Phi, H/C-Ratio");
        for (idx, perf) in all_perfs.iter().enumerate() {
            println!(
                "{idx},{},{}-{}-{},{:?},{:.3},{:?},{:.3}",
                perf.security_id,
                perf.dlh_kline.id_date(),
                perf.dll_kline.id_date(),
                perf.kline.id_date(),
                perf.dllow_high_phi(),
                perf.dllow_high_ratio(),
                perf.curr_high_phi(),
                perf.curr_high_ratio()
            );
        }
    }

    #[tokio::test]
    async fn test_run_buy_sell() {
        let _ = db::init().await.unwrap();

        pretty_env_logger::init();

        let securities = get_strategy_test_securities(
            1 == 1,
            Some(Market::SH),
            Some(security::SecurityType::STK),
            0,
            3000
        ).await;

        let mut sec_perfs = HashMap::new();
        for (idx, security) in securities.iter().enumerate() {
            let security_id = security.id_raw();
            let worker_no = format!("{idx}/{}", securities.len());

            let rs = SharpDropRebounceStrategyWorker::new(security_id.as_str(), &Klt::DAY).await;

            match rs {
                Ok(mut worker) => {
                    worker.run_buy_sell().await;

                    println!("[{}] {}***************\n", worker_no, security_id);
                    println!(
                        "#, security_id, buy_id, buy_price,sell_id,sell_price,profit_amount, profit_percent%, phi, ratio%"
                    );
                    for (idx, perf) in worker.performances.iter().enumerate() {
                        println!(
                            "{idx},{},{},{},{},{},{},{},{}",
                            security_id,
                            perf.buyorder_id,
                            perf.buy_price,
                            perf.sellorder_id,
                            perf.sell_price,

                            perf.cross_profit(),
                            perf.cross_profit_percent(),
                            perf.message
                        );
                    }
                    trace!("[{}] {}***************\n", worker_no, security_id);

                    sec_perfs.insert(security_id.clone(), worker.performances);
                }

                Err(e) => {
                    info!("err={:?}", e);
                }
            }
        }
        print_strategy_performance_summary(sec_perfs);
    }
    //
}
